Redesign model picker with favorites and search#2153
Redesign model picker with favorites and search#2153juliusmarminge merged 29 commits intopingdotgg:mainfrom
Conversation
- Replace provider submenus with sidebar-based model selection - Add model search, favorites, and locked-provider handling - Update settings schema and tests for favorite model persistence
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review This PR introduces a major new feature (model picker redesign with favorites, search, and provider sidebar) with ~2800 lines of new code including new components, state management, and keybindings. Additionally, a HIGH severity review comment identifies that a debug tool (react-scan) is committed to production HTML and would load for all users, which must be removed before merging. You can customize Macroscope's approvability policy. Learn more. |
There was a problem hiding this comment.
Pull request overview
This PR redesigns the chat model picker UI to make large model catalogs easier to navigate by introducing a sidebar-based provider filter, inline search, and per-model favorites persisted in client settings.
Changes:
- Add a
favoritesarray to client settings and update persistence tests accordingly. - Replace the provider submenu model picker with a new
ModelPickerContent(sidebar + searchable flat list) and supporting components/utilities. - Update UI styling (thin scrollbar) and adjust browser tests for the new picker behavior.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/contracts/src/settings.ts | Adds favorites to client settings and extends the settings patch schema. |
| apps/web/src/localApi.test.ts | Updates expected persisted client settings to include favorites. |
| apps/web/src/index.css | Adds thin scrollbar styling for the model picker list. |
| apps/web/src/components/chat/providerIconUtils.ts | Introduces shared provider icon mapping and provider/model labeling helpers. |
| apps/web/src/components/chat/ProviderModelPicker.tsx | Switches the picker popup to render the new ModelPickerContent. |
| apps/web/src/components/chat/ProviderModelPicker.browser.tsx | Updates UI tests for the new sidebar/search/favorites interactions. |
| apps/web/src/components/chat/ModelPickerSidebar.tsx | New sidebar component for provider/all/favorites filtering. |
| apps/web/src/components/chat/ModelPickerContent.tsx | New main picker UI with search, favorites persistence, and model list rendering. |
| apps/web/src/components/chat/ModelListRow.tsx | New row component for model selection + favorite toggle UI. |
| apps/web/src/components/CommandPalette.logic.ts | Refactors item construction to use Object.assign while preserving conditional fields. |
| apps/desktop/src/clientPersistence.test.ts | Updates desktop client settings persistence test fixture to include favorites. |
| .codex | File present in PR contents (no diff shown). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Replace the conditional skip with an explicit expect so the test fails loudly if the Codex sidebar button is absent and proceeds to click and assert model filtering afterward
Filter the All Models section to omit models already shown in Favorites, adjust provider visibility logic, and add a test ensuring favorites aren't duplicated across sections
Add a ClientSettingsPatch schema/type and export the ServerSettingsPatch type. Update apps/web useSettings to import patch types as type-only and use ClientSettingsPatch for the clientPatch return value.
Remove persisted client settings key before and after the ProviderModelPicker test to avoid state leakage. Use an aria-label based lookup for the favorite star button, assert its initial label, click it, and verify the aria-label toggles accordingly.
Replace global document.body.textContent checks with helpers that query the .model-picker-list element, and find favorited rows via filtered querySelectorAll to improve test reliability
- Add model picker toggle and jump shortcuts - Surface picker state in composer and sidebar hints - Update tests for keybinding and picker behavior
- boost favorite models in search ranking - switch model picker to combobox/popover interactions - improve scroll overflow handling and navigation tests
- Switch model picker toggle to `mod+shift+m` - Add favorites-focused model picker UI tweaks and provider icons - Remove desktop shortcut bridge plumbing no longer needed
- Add the `new` badge to Cursor in the provider picker sidebar
- Remove provider-specific icon class helper - Use shared sizing and viewport styling in the model picker
| for (const command of MODEL_PICKER_JUMP_KEYBINDING_COMMANDS) { | ||
| const shortcut = findEffectiveShortcutForCommand(keybindings, command, options); | ||
| if (!shortcut) continue; | ||
| if (matchesShortcutModifiers(modifiers, shortcut, platform)) { |
There was a problem hiding this comment.
Unused exported model picker hint functions
Low Severity
shouldShowModelPickerJumpHints and shouldShowModelPickerJumpHintsForModifiers are new exported functions that are never called in any production code. They are only referenced in keybindings.test.ts. The model picker content component (ModelPickerContent) unconditionally renders jump labels when the picker is open, so these "should show hints" functions serve no purpose outside of tests. This is dead code that adds maintenance burden.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit fc65ff8. Configure here.
| }, | ||
| }, | ||
| }; | ||
| ); |
There was a problem hiding this comment.
Unrelated refactoring of command palette thread items
Low Severity
The return statement in buildCommandPaletteThreadItems was refactored from spread syntax to Object.assign without any functional reason related to the model picker redesign. This increases the diff size, changes string quoting from "" to backtick templates, and introduces a different (arguably less readable) pattern while the original spread syntax was clear and idiomatic. This change makes the PR harder to review without any benefit.
Reviewed by Cursor Bugbot for commit fc65ff8. Configure here.
- Avoid redundant store updates when modifier values are unchanged - Track bare modifier keydown and keyup events explicitly - Load react-scan in the web app shell
- Keep the keyboard event test helper on one line - No behavior change
| <meta name="theme-color" content="#161616" /> | ||
| <link rel="icon" href="/favicon.ico" sizes="48x48" /> | ||
| <link rel="apple-touch-icon" href="/apple-touch-icon.png" /> | ||
| <script crossorigin="anonymous" src="//unpkg.com/react-scan/dist/auto.global.js"></script> |
There was a problem hiding this comment.
Debug tool react-scan committed to production HTML
High Severity
The react-scan development debugging script is loaded from unpkg CDN in the production index.html. This tool renders visual overlays highlighting React re-renders and adds significant performance overhead. It will load for every user on every page load, degrading performance and showing debug UI in production.
Reviewed by Cursor Bugbot for commit 28445c5. Configure here.
- Preserve canonical model names and sub-provider labels separately - Update picker search, trigger text, and favorites rendering - Extend server contracts for `shortName` and `subProvider`
- Split model picker visibility out of shortcut modifier state - Update sidebar and provider picker to use the new store
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 4 total unresolved issues (including 3 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d3119a9. Configure here.
| toggleModelPicker: () => { | ||
| setIsComposerModelPickerOpen((open) => !open); | ||
| }, | ||
| isModelPickerOpen: () => isComposerModelPickerOpen, |
There was a problem hiding this comment.
Stale closure in isModelPickerOpen imperative handle method
Medium Severity
The isModelPickerOpen method in the imperative handle captures isComposerModelPickerOpen by value. When toggleModelPicker() is called (e.g., via Mod+Shift+M), the state update is asynchronous — the handle isn't recreated until React commits the re-render. During this window, composerRef.current?.isModelPickerOpen() in ChatView.tsx's keydown handler returns the stale pre-toggle value. This means the shortcutContext.modelPickerOpen fed to resolveShortcutCommand can be wrong, causing e.g. thread.jump.1 to resolve instead of modelPicker.jump.1 immediately after toggling the picker open. Using a ref for the open state (or reading from the global Zustand store directly) would avoid this race.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit d3119a9. Configure here.
- Use the active provider's first model when the current slug belongs to another provider - Add a regression test for stale cross-provider model labels
Upstream additions: - fix(web): restore manual sort drag and keep per-group expand state (pingdotgg#2221) - fix: Change right panel sheet to be below title bar / action bar (pingdotgg#2224) - Refactor OpenCode lifecycle and structured output handling (pingdotgg#2218) - effect-codex-app-server (pingdotgg#1942) - Redesign model picker with favorites and search (pingdotgg#2153) - fix(server): prevent probeClaudeCapabilities from wasting API requests (pingdotgg#2192) - fix(server): handle OpenCode text response format in commit message gen (pingdotgg#2202) - Devcontainer / IDE updates (pingdotgg#2208) - Expand leading ~ in Codex home paths before exporting CODEX_HOME (pingdotgg#2210) - fix(release): use v<semver> tag format for nightly releases (pingdotgg#2186) Fork adaptations: - Took upstream's redesigned model picker with favorites and search - Removed deleted codexAppServerManager (replaced by effect-codex-app-server) - Stubbed fetchCodexUsage (manager-based readout no longer available) - Extended PROVIDER_ICON_BY_PROVIDER for all 8 fork providers - Extended modelOptionsByProvider test fixtures for all 8 providers - Inline ClaudeSlashCommand type (not yet re-exported from SDK) - Updated SettingsPanels imports for new picker module structure - Preserved fork's CI customizations (ubuntu-24.04 not Blacksmith)
Resolve conflict in apps/web/src/components/Sidebar.tsx by keeping clampSidebarThreadPreviewCount (still used by this PR's stepper input) and dropping threadJumpLabelMapsEqual, which upstream removed along with its callers in the model picker redesign (pingdotgg#2153).


What Changed
Replace model picker with new model picker inspired by t3chat (a lot). Has search, favorites, etc.
For OpenCode models, the upstream provider from OpenCode is extracted out and used under the model name.
Why
Adding OpenCode made me very happy but also, the old model picker is cumbersome with so many models, difficult to navigate. Also generally just a nice to have improvement.
UI Changes
Before:
After:
Checklist
This PR is small and focused
I feel bad not checking "small" but this is an XL pr so.
I explained what changed and why
I included before/after screenshots for any UI changes
Note
Medium Risk
Medium risk due to a large UI refactor that changes model selection flow, introduces new keybinding commands/contexts, and extends settings/contracts schemas (favorites + model metadata), which could affect persistence and shortcut resolution across desktop/web/server.
Overview
Redesigns the model picker into a popover-based experience with a provider sidebar, fuzzy search, and per-model favorites (persisted in new
ClientSettings.favorites), including new components for the picker UI and search/ranking.Adds model-picker keyboard support: new default keybindings (
modelPicker.toggleandmodelPicker.jump.1-9) plus amodelPickerOpencontext, with global modifier-state tracking to control jump-hint visibility and shortcut routing.Updates model metadata contracts to carry
shortName/subProvider(notably for OpenCode flattening), adjusts/modelto open the picker instead of inserting text, and refactors related tests and UI primitives (combobox/scroll-area) to support the new picker behavior.Reviewed by Cursor Bugbot for commit ad3f024. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Redesign model picker with sidebar, favorites, fuzzy search, and keyboard jump shortcuts
scoreModelPickerSearch.ClientSettingsthat pins favorite models to the top of the list and applies a ranking boost during search.modelPicker.jump.1–9) activated while the picker is open, and a toggle shortcut (modelPicker.toggleon Shift+Mod+M)./modelslash command now opens the model picker instead of injecting a model search into the composer command menu, and theslash-modeltrigger kind is removed fromdetectComposerTrigger.modelPickerOpenis factored into shortcut context across the sidebar and chat view.subProviderand use plain model name fornameinflattenOpenCodeModels, andServerProviderModelschema is extended with optionalshortName/subProviderfields.ProviderModelPickerfalls back to the active provider's first model when the current slug does not belong to that provider.Macroscope summarized ad3f024.